/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.webos.java.rssreader;

import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;
import java.io.InputStreamReader;
import java.net.URL;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.json.JSONObject;
import org.webos.java.base.Frame;
import org.webos.java.base.FrameType;
import org.webos.java.base.NetworkManager;
import org.webos.java.base.Notify;
import org.webos.java.base.RemoteMessage;
import org.webos.java.base.SystemManager;
import org.webos.java.base.dialog.QuestionDialog;
import org.webos.java.base.tree.TreeController;
import org.webos.java.base.tree.TreeControllerItem;
import org.webos.java.root.Log;
import org.xml.sax.InputSource;


/**
 *
 * @author Kostya
 */


@FrameType(code="folders", jsClass="rssreader.FoldersFeeds")
public class FoldersFeeds extends Frame<RSSReader>{

    class FeedItem extends TreeControllerItem<String, TreeControllerItem>{

        class UpdateThread {

            Timer timer = null;
            boolean stopped = false;

            public int updateFeed(){
                if(stopped)
                    return 0;
                int newCount = 0;
                if(timer==null){
                    //Do update
                    FolderItem parent = (FolderItem) getParent();
                    int firstID = -1;
                    synchronized(getApplication().connection){
                        try {
                            Log.debug(this, "Updating "+name+": "+url);
                            URL feedUrl = new URL(url);
                            SyndFeedInput input = new SyndFeedInput();
                            SyndFeed feed = input.build(new InputSource(feedUrl.openStream()));
                            List<SyndEntryImpl> entries = feed.getEntries();
//                            Log.debug(this, "Feed: "+feed.getDescription(), "entries:", entries.size());
                            PreparedStatement st = getApplication().connection.
                                    prepareQuery("insert into ENTRIES " +
                                    "(URI, URL, TITLE, BODY, PUBLISH_TIME, FETCH_TIME, ID_FOLDER, ID_FEED) values " +
                                    "(?, ?, ?, ?, ?, ?, ?, ?)");
                            PreparedStatement check = getApplication().connection.
                                    prepareQuery("select ID from ENTRIES where URI=? and id_feed=?");
                            for (SyndEntryImpl entry : entries) {
//                                Log.debug("Entry: ", entry.getTitle(), entry.getPublishedDate(),
//                                        entry.getLink(), entry.getUri(), entry.getDescription().getMode(),
//                                        entry.getDescription().getType(), entry.getDescription().getValue().length());
                                String title = entry.getTitle();
                                Date publishDate = entry.getPublishedDate();
                                if(publishDate==null)
                                    publishDate = new Date();
                                Date fetchDate = new Date();
                                String eURL = entry.getLink();
                                String eURI = entry.getUri();
                                StringBuffer body = new StringBuffer();
                                List<SyndContent> contents = entry.getContents();
                                for (SyndContent syndContent : contents) {
                                    body.append("<p>");
                                    body.append(syndContent.getValue());
                                    body.append("</p>");
                                }
                                if(contents.size()==0)
                                    body.append(entry.getDescription().getValue());
                                check.setString(1, eURI);
                                check.setInt(2, id);
                                ResultSet rset = check.executeQuery();
                                if(rset.next() || body.length()>=20000)
                                    continue;
                                st.setString(1, eURI);
                                st.setString(2, eURL);
                                st.setString(3, title);
                                st.setString(4, body.toString());
                                st.setTimestamp(5, new Timestamp(publishDate.getTime()));
                                st.setTimestamp(6, new Timestamp(fetchDate.getTime()));
                                st.setInt(7, parent.id);
                                st.setInt(8, id);
                                try {
                                    st.execute();
                                    if(firstID==-1)
                                        firstID = getApplication().connection.getLastID();
                                    newCount++;
                                } catch (Throwable t) {
                                    Log.error(this, "Exception thrown: " + t.getMessage());
                                    t.printStackTrace();
                                }
                            }
                            lastUpdate = new Date();
                            updateStatus();
                            PreparedStatement upd = getApplication().connection.
                                    prepareQuery("update FEEDS set UPDATE_TIME=? where id=?");
                            upd.setTimestamp(1, new Timestamp(lastUpdate.getTime()));
                            upd.setInt(2, id);
                            upd.execute();
                            PreparedStatement forDelete = getApplication().connection.
                                    prepareQuery("select id " +
                                    "from ENTRIES where id_feed=? " +
                                    "order by publish_time desc limit 9999 offset "+itemsTotal);
                            forDelete.setInt(1, id);
                            ResultSet del = forDelete.executeQuery();
                            PreparedStatement delst = getApplication().connection.
                                    prepareQuery("delete from ENTRIES where id=?");
                            while(del.next()){
                                delst.setInt(1, del.getInt(1));
                                delst.execute();
                            }
                            getApplication().connection.commit();
                            addNewItems(newCount);
                            if(newCount>0 && showNotify){
                                Notify updateOK = new Notify("Feed '"+SystemManager.prepareTextForView(name, true)+"' updated",
                                        "New "+newCount+" items in feed '"+SystemManager.prepareTextForView(name, true)+"'");
                                updateOK.setIcon("rss_icn rss_icn_rss");
                                getApplication().addNotify(updateOK, getApplication().foldersFeeds);
                            }

                        } catch (Throwable t) {
                            getApplication().connection.rollback();
                            Log.error(this, "Exception thrown: " + t.getMessage());
                            t.printStackTrace();
                            if(showNotify){
                                Notify error = new Notify("Error updating feed", "There was an error updating feed '"+SystemManager.prepareTextForView(name, true)+"'");
                                error.setIcon("rss_icn rss_icn_rss");
                                error.setTimeout(2000);
                                error.setForceAutoRemove(true);
                                getApplication().addNotify(error, getApplication().foldersFeeds);
                            }
                        }

                    }

                    timer = new Timer();
                    timer.schedule(new TimerTask() {

                        @Override
                        public void run() {
                            timer = null;
                            updateFeed();
                        }
                    }, 1000 * 60 * updateTimeout);
                }
                return newCount;
            }

            public void cancel(){
                if(timer!=null){
                    try {
                        timer.cancel();
                    } catch (Throwable t) {
                        Log.error(this, "Exception thrown: " + t.getMessage());
                        t.printStackTrace();
                    }
                    timer = null;
                }
            }

            public int forceUpdate(){
                if(timer==null)
                    return updateFeed();
                else{
                    cancel();
                    return updateFeed();
                }
            }

            public void start(){
                try {
                    new Timer().schedule(new TimerTask() {

                        @Override
                        public void run() {
                            updateFeed();
                        }
                    }, 100);
                } catch (Throwable t) {
                    Log.error(this, "Exception thrown: " + t.getMessage());
                    t.printStackTrace();
                }
            }
        }

        UpdateThread updateThread = new UpdateThread();

        int id = 0;
        String name = "";
        String url = "";
        int unreadCount = 0;
        Date lastUpdate = null;

        public FeedItem(TreeController aController, int aID, String aName, String aURL) {
            super(aController, aName);
            setType(TreeControllerItem.TYPE_LEAF);
            name = aName;
            url = aURL;
            setId(aID);
            getData().putOpt("feed", true);
        }

        void updateUnreadCount(){
            unreadCount = 0;
            try {
                PreparedStatement count = getApplication().connection.
                        prepareQuery("select count(*) from ENTRIES where read=false and id_feed=?");
                count.setInt(1, id);
                ResultSet rset = count.executeQuery();
                if(rset.next()){
                    unreadCount = rset.getInt(1);
                }
                PreparedStatement upd = getApplication().connection.
                        prepareQuery("select UPDATE_TIME from FEEDS where id=?");
                upd.setInt(1, id);
                rset = upd.executeQuery();
                if(rset.next()){
                    lastUpdate = rset.getTimestamp(1);
                }
            } catch (Throwable t) {
                Log.error(this, "Exception thrown: " + t.getMessage());
                t.printStackTrace();
            }
        }

        void updateStatus(){
            if(getApplication().entries.feedItem==this){
                getApplication().entries.setStatus(lastUpdate==null?"":"Updated "+
                        Entries.dateFormat.format(
                        getApplication().getSession().normalizeDate(lastUpdate)));
            }
        }

        public void setId(int id) {
            this.id = id;
            setId("feed-"+Integer.toString(id));
        }



        @Override
        public void objectTOJSON(JSONObject aObject) {
            aObject.putOpt("icon_cls", "rss_icn rss_icn_rss");
            if(unreadCount>0){
                aObject.putOpt("name", "("+unreadCount+") "+name);
                aObject.putOpt("cls", "rss_unread_tree");
            }else
                aObject.putOpt("name", name);
        }

        @Override
        public void beforeRemove() {
            updateThread.cancel();
            updateThread.stopped = true;
        }

        @Override
        public boolean isVisible() {
            return unreadCount>0 || showRead;
        }

        void addNewItems(int aNewCount){
            unreadCount += aNewCount;
            if(unreadCount<0)
                unreadCount = 0;
            FolderItem parent = (FolderItem) getParent();
            parent.feedChanged();
            notifyModified();
        }
    }

    class FolderItem extends TreeControllerItem<String, FeedItem>{

        int id = 0;
        String name = "";
        int unreadCount = 0;

        public FolderItem(TreeController aController, int aID, String aName) {
            super(aController, aName);
            setCollapsed(false);
            setType(TreeControllerItem.TYPE_FOLDER);
            setComparator(new Comparator<FeedItem>() {

                public int compare(FeedItem o1, FeedItem o2) {
                    return o1.name.compareToIgnoreCase(o2.name);
                }
            });
            id = aID;
            name = aName;
            setId("folder-"+Integer.toString(id));
            getData().putOpt("folder", true);
        }

        @Override
        public boolean isVisible() {
            return unreadCount>0 || getAllChildrenCount()==0 || showRead;
        }

        void feedChanged(){
            int prevUnread = unreadCount;
            unreadCount = 0;
            for (int i = 0; i < getAllChildrenCount(); i++) {
                FeedItem item = getAllChildren(i);
                unreadCount += item.unreadCount;
            }
            if(prevUnread!=unreadCount){
                notifyModified();
                getApplication().feedsChanged();
            }
        }

        @Override
        public void objectTOJSON(JSONObject aObject) {
            aObject.putOpt("icon_cls", "rss_icn rss_icn_folder");
            if(unreadCount>0){
                aObject.putOpt("name", "("+unreadCount+") "+name);
                aObject.putOpt("cls", "rss_unread_tree");
            }else
                aObject.putOpt("name", name);
        }

    }


    TreeController<FolderItem> controller = new TreeController<FolderItem>(this);
    boolean showRead = getApplication().options.optBoolean("show_read", true);
    int updateTimeout = getApplication().options.optInt("update_min", 15);
    int itemsTotal = getApplication().options.optInt("items_age", 200);
    boolean showNotify = getApplication().options.optBoolean("show_notify", true);


    public FoldersFeeds(RSSReader aReader) {
        super(aReader, aReader.workspace);
        controller.setAutoMoveFocus(false);
        setTitle("Your subscriptions:");
        setIcon("rss_icn rss_icn_folders");
        controller.getRoot().setComparator(new Comparator<FolderItem>() {
            public int compare(FolderItem o1, FolderItem o2) {
                return o1.name.compareToIgnoreCase(o2.name);
            }
        });
        getData().putOpt("show_read", showRead);
        loadData();
    }

    @RemoteMessage
    public void newFolder(JSONObject aObject){
        new FolderEditor(getApplication(), true, 0, "");
    }

    @RemoteMessage
    public JSONObject newFeed(JSONObject aObject){
        if(controller.getSelected()==null)
            return NetworkManager.getErrorObject(1209, "First, select folder for new feed");
        FeedItem item = new FeedItem(controller, -1, "", "");
        if(controller.getSelected() instanceof FolderItem)
            item.setParent(controller.getSelected());
        else
            item.setParent(controller.getSelected().getParent());
        new FeedEditor(getApplication(), true, item);
        return null;
    }

    @RemoteMessage
    public void editItem(JSONObject aObject){
        if(controller.getSelected()!=null){
            if(controller.getSelected() instanceof FolderItem){
                FolderItem item = (FolderItem) controller.getSelected();
                new FolderEditor(getApplication(), false, item.id, item.name);
            }
            if(controller.getSelected() instanceof FeedItem){
                FeedItem item = (FeedItem) controller.getSelected();
                new FeedEditor(getApplication(), false, item);
            }
        }
    }

    @RemoteMessage
    public JSONObject removeItem(JSONObject aObject){
        if(controller.getSelected()!=null){
            if(controller.getSelected() instanceof FolderItem){
                FolderItem item = (FolderItem) controller.getSelected();
                int answer = new QuestionDialog(getApplication(),
                        "Are you sure want to remove folder '"+
                        SystemManager.prepareTextForView(item.name, true)+"'").ask();
                if(answer==0){
                    synchronized(getApplication().connection){
                        try {
                            PreparedStatement st = getApplication().connection.
                                    prepareQuery("delete from ENTRIES where id_folder=?");
                            st.setInt(1, item.id);
                            st.execute();
                            st = getApplication().connection.
                                    prepareQuery("delete from FEEDS where id_folder=?");
                            st.setInt(1, item.id);
                            st.execute();
                            st = getApplication().connection.
                                    prepareQuery("delete from FOLDERS where id=?");
                            st.setInt(1, item.id);
                            st.execute();
                            getApplication().connection.commit();
                            item.remove();
                        } catch (Throwable t) {
                            getApplication().connection.rollback();
                            Log.error(this, "Exception thrown: " + t.getMessage());
                            t.printStackTrace();
                            return NetworkManager.getErrorObject(1204, "Can't remove folder from DB");
                        }

                    }
                    getApplication().feedsChanged();
                }
            }
            if(controller.getSelected() instanceof FeedItem){
                FeedItem item = (FeedItem) controller.getSelected();
                int answer = new QuestionDialog(getApplication(),
                        "Are you sure want to remove feed '"+
                        SystemManager.prepareTextForView(item.name, true)+"'").ask();
                if(answer==0){
                    synchronized(getApplication().connection){
                        try {
                            PreparedStatement st = getApplication().connection.
                                    prepareQuery("delete from ENTRIES where id_feed=?");
                            st.setInt(1, item.id);
                            st.execute();
                            st = getApplication().connection.
                                    prepareQuery("delete from FEEDS where id=?");
                            st.setInt(1, item.id);
                            st.execute();
                            getApplication().connection.commit();
                            FolderItem folder = (FolderItem) item.getParent();
                            item.remove();
                            folder.feedChanged();
                        } catch (Throwable t) {
                            getApplication().connection.rollback();
                            Log.error(this, "Exception thrown: " + t.getMessage());
                            t.printStackTrace();
                            return NetworkManager.getErrorObject(1210, "Can't remove feed from DB");
                        }
                    }
                }
            }
        }
        return null;
    }

    @RemoteMessage
    public JSONObject moveFeed(JSONObject aData){
        synchronized(getApplication().connection){
            try {
                FeedItem feed = (FeedItem) controller.findAllItemByID(aData.optString("feed", ""));
                FolderItem folder = (FolderItem) controller.findAllItemByID(aData.optString("folder", ""));
                if(feed==null || folder==null)
                    return NetworkManager.getErrorObject(1212, "Can't move feed to folder");
                PreparedStatement st0 = getApplication().connection.
                        prepareQuery("update FEEDS set id_folder=? where id=?");
                st0.setInt(1, folder.id);
                st0.setInt(2, feed.id);
                st0.execute();
                st0 = getApplication().connection.
                        prepareQuery("update ENTRIES set id_folder=? where id_feed=?");
                st0.setInt(1, folder.id);
                st0.setInt(2, feed.id);
                st0.execute();
                getApplication().connection.commit();
                feed.remove();
                FolderItem oldFolder = (FolderItem) feed.getParent();
                oldFolder.feedChanged();
                folder.appendChild(feed);
                folder.feedChanged();
            } catch (Throwable t) {

                Log.error(this, "Exception thrown: " + t.getMessage());
                t.printStackTrace();
            }
        }
        return null;
    }

    boolean saveFeed(boolean aNewFeed, FeedItem aFeedItem){
        try {
            FolderItem parent = (FolderItem) aFeedItem.getParent();
            if(aNewFeed){
                PreparedStatement st = getApplication().connection.
                        prepareQuery("insert into FEEDS (name, url, id_folder) values (?, ?, ?)");
                st.setString(1, aFeedItem.name);
                st.setString(2, aFeedItem.url);
                st.setInt(3, parent.id);
                st.execute();
                aFeedItem.setId(getApplication().connection.getLastID());
                parent.appendChild(aFeedItem);
//                controller.setSelectedItem(aFeedItem);
                aFeedItem.updateThread.start();
            }else{
                PreparedStatement st = getApplication().connection.
                        prepareQuery("update FEEDS set name=? where id=?");
                st.setString(1, aFeedItem.name);
                st.setInt(2, aFeedItem.id);
                st.execute();
                aFeedItem.notifyModified();
            }
            getApplication().connection.commit();
            return true;
        } catch (Throwable t) {
            getApplication().connection.rollback();
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return false;
    }

    boolean saveFolder(boolean aNewFolder, int aID, String aName){
        try {
            if(aNewFolder){
                PreparedStatement st = getApplication().connection.
                        prepareQuery("insert into FOLDERS (name) values (?)");
                st.setString(1, aName);
                st.execute();
                FolderItem item = new FolderItem(controller, getApplication().connection.getLastID(), aName);
                controller.getRoot().appendChild(item);
                controller.setSelectedItem(item);
            }else{
                PreparedStatement st = getApplication().connection.
                        prepareQuery("update FOLDERS set name=? where id=?");
                st.setString(1, aName);
                st.setInt(2, aID);
                st.execute();
                FolderItem item = controller.getRoot().findItem("folder-"+Integer.toString(aID));
                if(item!=null){
                    item.name = aName;
                    item.notifyModified();
                }
            }
            getApplication().connection.commit();
            return true;
        } catch (Throwable t) {
            getApplication().connection.rollback();
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return false;
    }

    private void loadData(){
        try {
            PreparedStatement st = getApplication().connection.
                    prepareQuery("select id, name from FOLDERS");
            ResultSet rset = st.executeQuery();
            while(rset.next()){
                FolderItem item = new FolderItem(controller, rset.getInt(1), rset.getString(2));
                controller.getRoot().appendChild(item);
//                Log.debug(this, "loadData add new folder: "+item.name);
                PreparedStatement st2 = getApplication().connection.
                        prepareQuery("select id, name, url from FEEDS where id_folder=?");
                st2.setInt(1, rset.getInt(1));
                ResultSet rset2 = st2.executeQuery();
                int unreadCount = 0;
                while(rset2.next()){
                    FeedItem feedItem = new FeedItem(controller, rset2.getInt(1),
                            rset2.getString(2), rset2.getString(3));
                    feedItem.updateUnreadCount();
                    item.appendChild(feedItem);
//                    Log.debug(this, "loadData add new feed: "+feedItem.name+", "+feedItem.url);
                    unreadCount += feedItem.unreadCount;
                    feedItem.notifyModified();
                    feedItem.updateThread.start();
                }
                item.unreadCount = unreadCount;
                item.notifyModified();
                st2.close();
            }
            st.close();
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }

    @RemoteMessage
    public void changeShowRead(JSONObject aObject){
        showRead = !showRead;
        getData().putOpt("show_read", showRead);
        getApplication().options.putOpt("show_read", showRead);
        getApplication().setOption("options", getApplication().options.toString());
        controller.setSilentMode();
        for (int i = 0; i < controller.getRoot().getAllChildrenCount(); i++) {
            FolderItem folder = controller.getRoot().getAllChildren(i);
            folder.notifyModified();
            if(folder.isVisible()){
                for (int j = 0; j < folder.getAllChildrenCount(); j++) {
                    FeedItem feed = folder.getAllChildren(j);
                    feed.notifyModified();
                }
            }
        }
        controller.sendTree();
    }

    @RemoteMessage
    public JSONObject clickOnItem(JSONObject aData){
        try {
//            Log.debug(this, "Clicked on item: "+aData.optString("id"));
            TreeControllerItem item = controller.findItemByID(aData.optString("id"));
            if(item!=null){
                if(item instanceof FeedItem)
                    getApplication().entries.showFeed((FeedItem) item);
                else
                    getApplication().entries.showFolder((FolderItem) item);
                getApplication().showFrame(getApplication().entries);
            }
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return null;
    }

    @Override
    public void terminate() {
        controller.getRoot().clear();
    }

}
